﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;// Xna.Framework.Content.Pipeline.Serialization.Intermediate;
using System.IO;
using System.Drawing;
using Microsoft.Xna.Framework;

namespace DELVE_GAME
{

    public class Game
    {
        bool futureBool = false;
        public GameSettings gameSettings;
        protected List<Player> players;
        protected HashSet<Player> stillAlive;
        public Board board;
        public Game futureGame;

        protected GameLog gameLog;

        protected int turn;
        protected int currentPlayer;
        protected bool hasBases;
        protected Player player; //reference to current player
        protected bool gameOver;
        protected bool readOnly;
        protected TimeSpan turnStartTime;
        protected int remainingTime;

        #region getters and setters
        public Player getPlayer(int myPlayer) {
            return players[myPlayer];
        }
        public int getTurn() { return turn; }
        public Player getCurrentPlayer() {return player;}
        public void eliminatePlayer(Player p) 
        {
            stillAlive.Remove(p);
            p.die(turn);
            foreach (Player other in stillAlive)
            {
                other.resetAlliances();
            }
        }
        public bool isVisible(Tile tile) {
            bool visibility = true;
            if (gameSettings.getFogOfWar())
            {
                visibility = player.isVisible(tile);
                if(!visibility) {
                    foreach(Player p in stillAlive) {
                        if (player.isAlly(p) && p.isVisible(tile))
                        {
                            visibility = true;
                            break;
                        }
                    }
                }
            }
            return visibility;
        }
        public GameSettings getGameSettings()
        {
            return gameSettings;
        }
        internal int getCurrentPlayerIndex()
        {
            return currentPlayer;
        }

        internal bool tileInTerritory(Tile tile)
        {
            return board.tileInTerritory(tile, gameSettings.getGameType());
        }
        internal bool canPlaceBase(Tile tile)
        {
            // if first time && it's in starting area, okay
            // if not first time && no viruses && not occupied, okay
            bool hasPlaced = player.hasPlacedBase();
            return (!hasPlaced && board.tileInStartingArea(tile, currentPlayer)) ||
                    (!hasBases && hasPlaced && !tile.isOccupied() && !tileInTerritory(tile));
        }
        public List<int> canAllyWith()
        {

            int alliesAndTents = player.getNumAllies() + player.getNumTentativeAlliances();
            List<int> canAlly = new List<int>();
                foreach (Player p in stillAlive)
                {
                    int otherAlliancesAndTents = p.getNumAllies() + p.getNumTentativeAlliances();
                    if (player.isTentativeAlly(p) || 
                        (player != p && !player.isAlly(p) && 
                        alliesAndTents < stillAlive.Count - 2 && 
                        otherAlliancesAndTents < stillAlive.Count - 2))
                        canAlly.Add(p.getPlayerIndex());
                }
            return canAlly;
        }

        /*
         *Replays 
         */
        public GameLog getGameLog() { return gameLog; }
        public bool isReadOnly() { return readOnly; }

        /*
         * Time limit
         */

        public bool hasTimeLimit()
        {
            return gameSettings.getTimeLimit() != -1;
        }
        public int getRemainingTime()
        {
            return hasTimeLimit() ? remainingTime : -1;
        }

        #endregion

        public Game(){}

        /// <summary>
        /// This makes a deep copy of the game
        /// </summary>
        /// <param name="currentGame"></param>
        /*public Game(Game currentGame) {
            this.gameSettings = currentGame.gameSettings;
            this.players = new List<Player>(currentGame.players);
            this.stillAlive = new HashSet<Player>(currentGame.stillAlive);
            this.board = new Board(currentGame.board);
            //this.gameLog = currentGame.gameLog;
            this.turn = currentGame.turn;
            this.currentPlayer = currentGame.currentPlayer;
            this.hasBases = currentGame.hasBases;
            this.player = new Player(currentGame.player);
            this.gameOver = currentGame.gameOver;
            this.readOnly = currentGame.readOnly;
        }*/

        /*
         *create game, hardcoding board 
         */
        public Game(GameSettings gameSettings) {
            this.gameSettings = gameSettings;
            initializeGame();
            readOnly = false;

            gameLog = new GameLog();
            gameLog.addSettings(gameSettings);

            startTurn();
        }

        /*
         *create game, passing in board 
         */
        public Game(GameSettings gameSettings, Board board)
        {
            this.gameSettings = gameSettings;
            initializeGame(board);
            readOnly = false;

            gameLog = new GameLog();
            gameLog.addSettings(gameSettings);
            startTurn();
        }

        /*
         *create game from gamelog 
         */
        public Game(GameLog log)
        {
            gameLog = log;
            gameLog.openForReading();
            gameSettings = gameLog.settings;
            initializeGame();
            readOnly = true;
            startTurn();
        }

        #region automatic game logic

        protected void initializeGame()
        {
            hasBases = false;
            board = new Board();
            players = new List<Player>();
            for (int i = 0; i < gameSettings.getNumPlayers(); ++i)
            {
                players.Add(new Player(i));
                
            }

            foreach (Player p in players)
            {
                p.setPotency(gameSettings.getStartingPotencyGauge());
            }
            stillAlive = new HashSet<Player>(players);
            currentPlayer = 0;
            player = players[currentPlayer];
            turn = 1;
            gameOver = false;
        }

        protected void initializeGame(Board newBoard)
        {
            this.board = newBoard;
            players = new List<Player>();
            for (int i = 0; i < gameSettings.getNumPlayers(); ++i)
            {
                players.Add(new Player(i));
            }
            stillAlive = new HashSet<Player>(players);
            currentPlayer = 0;
            player = players[currentPlayer];
            turn = 1;
            gameOver = false;
        }

       

        /// <summary>
        /// This creates a FutureGame that is cycled through one complete turn for all players
        /// not allowing any players to make upgrades.
        /// </summary>
        public virtual void createFutureGame()
        {
            futureBool = true;
            futureGame = new Game();
            this.futureGame.gameSettings = this.gameSettings;
            
            this.futureGame.players = new List<Player>();
            foreach (Player p in this.players) {
                this.futureGame.players.Add(new Player(p));
            }



            this.futureGame.stillAlive = new HashSet<Player>();
            foreach(Player p in this.stillAlive)
            {
                foreach (Player fp in this.futureGame.players) { 
                    if(p.Equals(fp))
                        this.futureGame.stillAlive.Add(fp);
                }                
            }

            this.futureGame.board = new Board(this.board);
            //this.gameLog = currentGame.gameLog;
            this.futureGame.turn = this.turn;
            this.futureGame.currentPlayer = this.currentPlayer;
            int nextPlayer = this.futureGame.currentPlayer;
            this.futureGame.hasBases = this.hasBases;

            foreach (Player fp in this.futureGame.players) {
                if (fp.Equals(this.player))
                    this.futureGame.player = fp;
            }

            this.futureGame.gameOver = this.gameOver;
            this.futureGame.readOnly = true;

            int tempCurrentPlayer = this.futureGame.currentPlayer;

            foreach (Player sa in this.futureGame.stillAlive)
            {
                /*foreach (Tile t in sa.getVisible()) { 
                    this.futureGame.board.setLocation(t.getLocation())
                }*/
                foreach (Virus v in sa.getMyViruses())
                {
                    if (v.getTile() != null)
                    {
                        v.setTile(this.futureGame.board.getLocation(v.getTile().getLocation()));
                        this.futureGame.board.setLocation(v.getTile().getLocation(), v);
                    }
                }
            }

//            this.futureGame.board.printBoard();
            this.futureGame.barrier();
//            this.futureGame.board.printBoard();
        }

        public virtual void createFutureGameHelper() {
            createFutureGame();
            this.player.alertViruses = new List<Virus>();
            foreach (Virus v in this.player.getMyViruses())
            {
                this.player.alertViruses.Add(v);
            }
            //this foreach loop makes the alertVirus list represent all of the viruses that die off in the future
            int temp = 0;
            foreach (Virus v in this.futureGame.player.getMyViruses())
            {
                temp++;
                if (this.player.alertViruses.Contains(v))
                    this.player.alertViruses.Remove(v);
            }
            this.player.futurePotencyGain = this.futureGame.player.getPotency() - this.player.getPotency();

        }

        protected void startTurn() 
        {
            gameLog.addTurn(currentPlayer);
            turnStartTime = TimeSpan.Zero; //Flag field for Update
            hasBases = player.startTurn();
            if (hasBases)
            {
                createFutureGameHelper();
                
                /*
                for (int i = 0; i < this.board.getHeight(); i++)
                {
                    for (int j = 0; j < this.board.getWidth(); j++)
                    {
                        Virus first = this.board.getLocation(new Point(j, i)).getVirus();
                        Virus second = this.futureGame.board.getLocation(new Point(j, i)).getVirus();
                        Tile secondTile = this.futureGame.board.getLocation(new Point(j, i));
                        if (this.board.getLocation(new Point(j, i)) == this.futureGame.board.getLocation(new Point(j, i)))
                        {
                            //System.Console.WriteLine("they are equal");
                            System.Diagnostics.Debug.WriteLine("Tiles are equal");
                        }
                        if (first != null && this.board.getLocation(new Point(j, i)).getVirus() == this.futureGame.board.getLocation(new Point(j, i)).getVirus())
                        {
                            System.Diagnostics.Debug.WriteLine("Viruses are equal");
                        }
                        if(first !=null && second != null && first.getTile() == second.getTile()){
                            System.Diagnostics.Debug.WriteLine("Viruses Tiles are equal");
                        }
                        if (second !=null && second.getTile() == secondTile){
                            //System.Diagnostics.Debug.WriteLine("Tile equals virus Tile");
                        }
                    }
                }
                foreach (Player p in this.players) {
                    foreach (Player fp in this.futureGame.players) {
                        if (p == fp) {
                            System.Diagnostics.Debug.WriteLine("Players are equal");
                        }
                        if(p.Equals(fp)){
                            for(int i = 0; i < p.getMyViruses().Count; i ++){
                                if(p.getMyViruses()[i] == fp.getMyViruses()[i]){
                                    System.Diagnostics.Debug.WriteLine("Player viruses are equal");
                                }
                            }
                            
                        }
                        
                    }
                }
                foreach (Player p in this.stillAlive) { 
                    foreach (Player fp in this.futureGame.stillAlive){
                        if(p.Equals(fp)){
                            if(p == fp){
                                System.Diagnostics.Debug.WriteLine("StillAlive are equal");
                            }
                        }
                    }
                }
                if (this.player == this.futureGame.player) {
                    System.Diagnostics.Debug.WriteLine("Current player is equal");
                }*/



            }


        }

        public virtual void endTurn() { 
            player.endTurn();       
        }

        protected void awardPoints(Player p)
        {
            int playerNum = p.getPlayerIndex();
            int occupying = 0;
            switch(gameSettings.getGameType()) {
                case (int)GameType.TERRITORIES:
                    occupying = board.occupyingTerritories(playerNum);                  
                    break;
                case (int)GameType.TERRITORY_RACE:
                    if (board.occupyingRaceTerritory(playerNum)) occupying = 1;
                    break;
            }
            p.addPoints(occupying);
        }

        public virtual bool isGameOver()
        {
            if (gameOver) return true;
            gameOver = false;
            switch (gameSettings.getGameType())
            {
                case (int)GameType.QUICK_WIN:
                case (int)GameType.TERRITORIES:
                    gameOver = turn > gameSettings.getTurnLimit();
                    break;
                case (int)GameType.ENDURANCE:
                    foreach(Player p in stillAlive)
                    {
                        if (p.getNumViruses() == board.getOccupiableTiles()) {
                            gameOver = true;
                            break;
                        }
                    }
                    break;
                case (int)GameType.TERRITORY_RACE:
                    gameOver = (player.getPoints() == 1);
                    break;
            }
            if (stillAlive.Count == 1)
            {
                gameOver = true;
            }
            return gameOver;
        }

        /*
         *Function updates time counter, return true if
         *player turn length has run over time limit
         */
        public bool Update(GameTime gameTime)
        {
            //If new player turn, reset turnStartTime
            if (turnStartTime.Equals(TimeSpan.Zero))
            {
                turnStartTime = gameTime.TotalGameTime;
            }
            TimeSpan nowTime = gameTime.TotalGameTime - turnStartTime;
            int timeLimit = gameSettings.getTimeLimit();
            remainingTime = timeLimit - nowTime.Seconds;// - turnStartTime.Seconds);
            return (timeLimit != -1 && nowTime.Seconds > timeLimit);
            
        }

        Player winner = null;
        public Player getWinner()
        {
            if (winner != null) return winner;
            if (stillAlive.Count == 1) winner = stillAlive.ElementAt(0);
            switch (gameSettings.getGameType())
            {
                case (int)GameType.QUICK_WIN:
                    //Winner has most viruses in turn limit
                    int maxViruses = players[0].getPoints();
                    foreach (Player p in players)
                    {
                        if (p.getNumViruses() > maxViruses)
                            winner = p;
                    }
                    break;
                case (int)GameType.ENDURANCE:
                    //Winner is last man standing or has entire board
                    foreach (Player p in stillAlive)
                    {
                        if (p.getNumViruses() == board.getOccupiableTiles())
                        {
                            winner = p;
                            break;
                        }
                    }
                    break;
                case (int)GameType.TERRITORIES:
                    //Winner has most points in turn limit
                    int maxPoints = -1;
                    foreach (Player p in players)
                    {
                        if (p.getPoints() > maxPoints)
                        {
                            maxPoints = p.getPoints();
                            winner = p;
                        }
                    }
                    break;
                case (int)GameType.TERRITORY_RACE:
                    //Winner gets territory point first
                    if (player.getPoints() == 1) winner = player;
                    break;
            }
            winner.wonGame(turn);
            return winner;
        }

        /*
         *Changes current player, eliminates dead ones 
         */
        public virtual bool changeTurn()
        {
            if (!isReadOnly())
            {
                gameLog.addAction((int)ACTIONS.END_TURN);
                endTurn();
            }
            do
            {
                if (!player.isAlive()) eliminatePlayer(player);
                currentPlayer++;
                if (currentPlayer == players.Count)
                {
                    turn++;
                    barrier();
                    currentPlayer %= players.Count;
                }
                player = players[currentPlayer];
            } while (!player.isAlive());
            startTurn();

            return isGameOver();
        }

        /*
         * Virus expansion and manipulation are handled here
         */
        public void barrier()
        {
            List<Virus> newViruses = new List<Virus>();
            foreach (Player p in stillAlive)
            {
                p.ageViruses();
            }
            foreach (Player p in stillAlive)
            {
                newViruses.AddRange(p.expand());
            }
            foreach (Virus v in newViruses)
            {
                players[v.getPlayerIndex()].addVirus(v);
            }
            foreach (Player p in stillAlive)
            {
                awardPoints(p);
                p.collectVisibleTiles();
            }
        }

        #endregion

        #region user input

        public List<int> doMove(Tile tile)
        {
            List<int> validUpgrades = null;
            if (player.isAlive())
            {
                if (!hasBases)
                {
                    if (canPlaceBase(tile) && player.placeBase(tile) && !isReadOnly())
                            gameLog.addAction((int)ACTIONS.PLACE_BASES, tile.getLocation());
                }
                else validUpgrades = player.displayUpgrades(tile);
            }
            return validUpgrades;
        }
        public List<int> doMove(List<Tile> tiles)
        {
            List<int> validUpgrades = null;
            if (player.isAlive())
            {
                if (!hasBases && tiles.Count * Virus.GET_BASE_COST() <= player.getPotency())
                {
                    foreach (Tile t in tiles) doMove(t);
                }
                else validUpgrades = player.displayUpgrades(tiles);
            }
            return validUpgrades;
        }

        public void applyUpgrade(Tile tile, int upgrade)
        {
            if (Virus.isFriend(getCurrentPlayerIndex(), tile.getVirus()))
            {
                if (!isReadOnly()) gameLog.addAction((int)ACTIONS.UPGRADE, tile.getLocation(), upgrade);
                player.applyUpgrade(tile.getVirus(), upgrade);
                this.createFutureGameHelper();
            }
        }
        public void applyUpgrades(List<Tile> tiles, int upgrade)
        {
            foreach (Tile t in tiles)
            {
                if(t.isOccupied() && t.getVirus().getPlayerIndex() == currentPlayer) {
                    Virus v = t.getVirus();
                    if(v.getPlayerIndex() == currentPlayer && v.isUpgradable(player.getPotency(),upgrade))
                        applyUpgrade(t, upgrade);
                }
            }
        }


        public bool sendAllianceRequest(int playerIndex)
        {
            return player.sendAllianceRequest(players[playerIndex], stillAlive.Count);
        }

        public void respondToRequest(int playerIndex, bool accept)
        {
            player.respondToRequest(players[playerIndex], accept);
            if (accept && !isReadOnly())
                gameLog.addAction((int)ACTIONS.ACCEPT_REQUEST, playerIndex);
        }

        //Both sides
        public void breakAlliance(int playerIndex)
        {
            player.breakAlliance(players[playerIndex]);
            if (!isReadOnly()) gameLog.addAction((int)ACTIONS.ACCEPT_REQUEST, playerIndex);
        }
        #endregion

        #region replays

        //Returns true if turn is changed
        public void doTurn()
        {
            Action thisAction = gameLog.performLatestAction();
            int actionIndex = thisAction.getActionIndex();
            Tile tile;
            int playerIndex;
            bool turnChanged = false;
            while (!turnChanged)
            {
                switch (thisAction.getActionIndex())
                {
                    case (int)ACTIONS.PLACE_BASES:
                        tile = board.getLocation(thisAction.getLocation());
                        doMove(tile);
                        break;
                    case (int)ACTIONS.UPGRADE:
                        tile = board.getLocation(thisAction.getLocation());
                        int upgrade = thisAction.getUpgradeType();
                        applyUpgrade(tile, upgrade);
                        break;
                    case (int)ACTIONS.SEND_REQUEST:
                        playerIndex = thisAction.getPlayerIndex();
                        sendAllianceRequest(playerIndex);
                        break;
                    case (int)ACTIONS.ACCEPT_REQUEST:
                        playerIndex = thisAction.getPlayerIndex();
                        respondToRequest(playerIndex, true);
                        break;
                    case (int)ACTIONS.BREAK_ALLIANCE:
                        playerIndex = thisAction.getPlayerIndex();
                        breakAlliance(playerIndex);
                        break;
                    case (int)ACTIONS.END_TURN:
                        turnChanged = true;
                        break;
                }
            }
        }

        #endregion
    }

}
